
#include "GraphicsLayer.h"

using namespace std;


/**
 * Sigleton para el unico GraphicsLayer. Solo puede haber uno por aplicacion
 */
cGraphicsLayer* cGraphicsLayer::m_pGlobalGLayer = NULL;

cGraphicsLayer::cGraphicsLayer() {
    if (m_pGlobalGLayer)
        printf("cGraphicsLayer ya fue instanciado\n");

    m_pGlobalGLayer = this;
}

void cGraphicsLayer::DestroyAll() {
    delete this->cShader;
    this->cShader = NULL;
    /**
     * Previene el cualquier porximo intento de acceso a la clase graphics
     */
    m_pGlobalGLayer = NULL;
    delete m_pGlobalGLayer;
}

cGraphicsLayer::~cGraphicsLayer() {
    DestroyAll();
}

void cGraphicsLayer::SetColorClear(const float (&colorClear)[4]) {
    // Se establece el color de fondo
    this->colorClear[0] = colorClear[0];
    this->colorClear[1] = colorClear[1];
    this->colorClear[2] = colorClear[2];
    this->colorClear[3] = colorClear[3];
}

void cGraphicsLayer::Clear() {
    // Se establece el color de fondo
    glClearColor(this->colorClear[0], this->colorClear[1], this->colorClear[2], this->colorClear[3]);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}

void cGraphicsLayer::UpdateLights() {
}

void cGraphicsLayer::InitD3D() {
    this->automaticUpdate = true;
    
    float colorClear[4] = {0.1f, 0.1f, 1.0f, 1.0f};
    this->SetColorClear(colorClear);
    
    this->CreateLookAt();
    this->CreateViewport();    
    this->CreateDefaultShader();    
    this->GetDefaultMatrixInShader();    
    this->GetDefaultTextureInShader();
    this->DisableFace();
    this->SetDepth();
}

void cGraphicsLayer::DisableFace() {
    glDisable(GL_CULL_FACE);
}

void cGraphicsLayer::SetDepth(){
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDisable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
}

void cGraphicsLayer::CreateLookAt() {
    this->EyePosition[0] = 0.0f; // x
    this->EyePosition[1] = 0.0f; // y
    this->EyePosition[2] = -5.0f; // z

    this->ViewPoint[0] = 0.0f; // x
    this->ViewPoint[1] = 0.0f; // y
    this->ViewPoint[2] = 0.0f; // z

    this->UpVector[0] = 0.0f; // x
    this->UpVector[1] = 1.0f; // y
    this->UpVector[2] = 0.0f; // z
    
    // Camera matrix
    this->matrixView = glm::lookAt(
        glm::vec3(EyePosition[0], EyePosition[1], EyePosition[2]),
        glm::vec3(ViewPoint[0], ViewPoint[0], ViewPoint[0]),
        glm::vec3(UpVector[0], UpVector[0], UpVector[0])
    );
}

void cGraphicsLayer::UpdateFrustum(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar) {
    this->cFrustum[0] = fovy;
    this->cFrustum[1] = aspect;
    this->cFrustum[2] = zNear;
    this->cFrustum[3] = zFar;
}

void cGraphicsLayer::CreateViewport() {
    // Se obtiene el tamaño de la ventana (puede ser diferente a la pedida)
    glfwGetWindowSize(&this->cWidth, &this->cHeight);

    // Caso especial: Se analiza la division por 0
    this->cHeight = this->cHeight > 0 ? this->cHeight : 1;
    
    matrixProjection = glm::perspective(45.0f, (GLfloat) this->cWidth / (GLfloat) this->cHeight, 1.0f, 10000.0f);
}

void cGraphicsLayer::CreateDefaultShader() {
    glShadeModel(GL_SMOOTH);

    std::string FilePathVertex = cApplication::GetApplication()->m_dFrameworkShaders;
    FilePathVertex.append("vertexshader.txt");

    std::string FilePathFragment = cApplication::GetApplication()->m_dFrameworkShaders;
    FilePathFragment.append("fragmentshader.txt");

    this->cShader = SM.loadfromFile((char*) FilePathVertex.c_str(), (char*) FilePathFragment.c_str()); // load (and compile, link) from file
    if (this->cShader == 0) {
        std::cout << "Error Loading, compiling or linking shader\n";
    }

    this->c_ProgramObject = this->cShader->GetProgramObject();
}
    
void cGraphicsLayer::GetDefaultMatrixInShader() {
    /*
     glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.f);
    glm::mat4 ViewTranslate = glm::translate(
        glm::mat4(1.0f),
        glm::vec3(0.0f, 0.0f, -Translate)
    );
    glm::mat4 ViewRotateX = glm::rotate(
        ViewTranslate,
        Rotate.y,
        glm::vec3(-1.0f, 0.0f, 0.0f)
    );
    glm::mat4 View = glm::rotate(
        ViewRotateX,
        Rotate.x,
        glm::vec3(0.0f, 1.0f, 0.0f)
    );
    glm::mat4 Model = glm::scale(
        glm::mat4(1.0f),
        glm::vec3(0.5f)
    );
    glm::mat4 MVP = Projection * View * Model;
    glUniformMatrix4fv(LocationMVP, 1, GL_FALSE, glm::value_ptr(MVP));
     */
    
    // Model matrix : an identity matrix (model will be at the origin)
    this->matrixModel = glm::mat4(1.0f);  // Changes for each model !
    
    // Our ModelViewProjection : multiplication of our 3 matrices
    this->matrixMVP = this->matrixProjection * this->matrixView * this->matrixModel; // Remember, matrix multiplication is the other way around
    
    // Only at initialisation time.
    // Get a handle for our "Projection" uniform.
    this->MatrixPID = glGetUniformLocation(this->c_ProgramObject, "mP");
    if (this->MatrixPID == -1) {
        std::cout << "Advertencia: la variable mP no ha sido encontrada o no está siendo utilizada!\n";
    }
    // Get a handle for our "View" uniform.
    this->MatrixVID = glGetUniformLocation(this->c_ProgramObject, "mV");
    if (this->MatrixVID == -1) {
        std::cout << "Advertencia: la variable mV no ha sido encontrada o no está siendo utilizada!\n";
    }
    // Get a handle for our "Model" uniform.
    this->MatrixMID = glGetUniformLocation(this->c_ProgramObject, "mM");
    if (this->MatrixMID == -1) {
        std::cout << "Advertencia: la variable mM no ha sido encontrada o no está siendo utilizada!\n";
    }
    // Get a handle for our "Model View Projection" uniform.
    this->MatrixMVPID = glGetUniformLocation(this->c_ProgramObject, "mMVP");
    if (this->MatrixMVPID == -1) {
        std::cout << "Advertencia: la variable mMVP no ha sido encontrada o no está siendo utilizada!\n";
    }
}

void cGraphicsLayer::GetDefaultTextureInShader() {
    this->inPositionTex = glGetUniformLocation(this->c_ProgramObject, "tex");
    if (this->inPositionTex == -1) {
        std::cout << "Advertencia: la variable tex no ha sido encontrada o no está siendo utilizada!\n";
    }
}

GLuint cGraphicsLayer::getProgramFromDefaultShader() {
    return this->c_ProgramObject;
}

void cGraphicsLayer::Create() {
    new cGraphicsLayer(); // Construye el objeto.

    // Inicializa Direct3D y el device
    Graphics()->InitD3D();
}

void cGraphicsLayer::SetNumActiveTexture() {
}

void cGraphicsLayer::UpdateMatrices() {
    if (this->automaticUpdate == true) {
        this->matrixMVP = this->matrixProjection * this->matrixView * this->matrixModel;
    }
}

void cGraphicsLayer::ScaleModel(float x, float y, float z) {
    this->ScaleModelData[0] = x; // x
    this->ScaleModelData[1] = y; // y
    this->ScaleModelData[2] = z; // z
}

void cGraphicsLayer::TranslateModel(float x, float y, float z) {
    this->TranslateModelData[0] = x; // x
    this->TranslateModelData[1] = y; // y
    this->TranslateModelData[2] = z; // z
}

void cGraphicsLayer::EnableAutomaticUpdate() {
    this->automaticUpdate = true;
}

void cGraphicsLayer::DisableAutomaticUpdate() {
    this->automaticUpdate = false;
}

GLuint cGraphicsLayer::LoadTextureFromGraphics(int id) {
    std::string texturePath = cApplication::GetApplication()->m_dAppTextures.c_str();
    const char* textureName = this->m_pTexturesNamesList[id].c_str();
    texturePath.append(textureName);
    
    glGenTextures(1, &this->m_pTexturesPointers[id]);
    glBindTexture(GL_TEXTURE_2D, this->m_pTexturesPointers[id]);

    if (glfwLoadTexture2D((char*) texturePath.c_str(), GLFW_BUILD_MIPMAPS_BIT)) {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        return this->m_pTexturesPointers[id];
    } else {
        std::cout << "Advertencia: no se pudo cargar la textura!\n";
        return -1;
    }
}

GLuint cGraphicsLayer::LoadTexture(char* filename) {
    GLuint Texture;
    glGenTextures(1, &Texture);
    glBindTexture(GL_TEXTURE_2D, Texture);

    if (glfwLoadTexture2D(filename, GLFW_BUILD_MIPMAPS_BIT)) {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        return Texture;
    } else {
        std::cout << "Advertencia: no se pudo cargar la textura!\n";
        return -1;
    }

    delete filename;
}

void cGraphicsLayer::SetTexture(GLuint m_pTexture) {
    // Se setea la textura
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_pTexture);
    glUniform1i(this->inPositionTex, 0);
}

void cGraphicsLayer::SendMatrix() {
    glUniformMatrix4fv(this->MatrixMID, 1, GL_FALSE, &this->matrixModel[0][0]);
    glUniformMatrix4fv(this->MatrixPID, 1, GL_FALSE, &this->matrixProjection[0][0]);
    glUniformMatrix4fv(this->MatrixVID, 1, GL_FALSE, &this->matrixView[0][0]);
    glUniformMatrix4fv(this->MatrixMVPID, 1, GL_FALSE, &this->matrixMVP[0][0]);
}

void cGraphicsLayer::CheckOpenGLVersion() {
    int major, minor, rev;
    glfwGetGLVersion(&major, &minor, &rev);
    fprintf(stderr, "\nVersion de OpenGL recibida: %d.%d.%d\n\n", major, minor, rev);
}

void cGraphicsLayer::EnableDefaultShader() {
    if (this->cShader) {
        this->cShader->begin();
    }
}

void cGraphicsLayer::DisableDefaultShader() {
    if (this->cShader) {
        this->cShader->end();
    }
}